package inz.model.patterns;

import inz.common.IOManager;
import inz.common.LoggingModule;
import inz.common.MyUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

/** Implementation of a sequence database. */
public class SequenceDatabase
{

	private static Logger logger = Logger.getLogger(LoggingModule.GSP_LOGGER);
	/** List of database sequences */
	private final List<DbSequence> sequences = new ArrayList<DbSequence>();
	/** Set of ignored words */
	private static Set<String> stopwords;
	/** if all sequences are taken into consideration during algorithm (division into training and test set) */
	private boolean excluded = false;
	private int inactiveIndexBegin, inactiveIndexEnd;
	static
	{
		stopwords = IOManager.loadFromFile("data\\strony_konferencji\\1\\stopwords_english");
	}
	public SequenceDatabase(Set<String> stopwords)
	{
		SequenceDatabase.stopwords = stopwords;
	}

	public SequenceDatabase()
	{
	}

	/** loads contents of files from given directory into program structures
	 * @param directory directory of files to be loaded
	 * @param filterWords should omit unmeaningful words
	 * @param filterSize should omit 1 letter words */
	public void loadFiles(String directory, boolean filterWords, boolean filterSize)
	{
		if (directory.isEmpty())
		{
			directory = ".";
		}
	
		try
		{
			File[] files = IOManager.getDirFiles(directory, IOManager.TXT);
			LoggingModule.startProgress("files", " Loading files ", files.length);
			for (File file : files)
			{
				Scanner fileScanner = IOManager.getFileScanner(file, IOManager.WORDS_NO_NUMBERS_DELIM_REGEX);
				DbSequence dbSequence = new DbSequence();
				String word;
				while (fileScanner.hasNext())
				{
					word = fileScanner.next().toLowerCase();
					if (filterSize && word.length() < 2)
					{
						continue;
					}
					if (filterWords && (stopwords != null && stopwords.contains(word)))
					{
						continue;
					}
					dbSequence.addElement(MyUtils.stem(word));
				}
				sequences.add(dbSequence);
				LoggingModule.tickProgress();
			}
			LoggingModule.endProgress();
			logger.log(Level.INFO, "loaded " + files.length + " files", "loading");
		}
		catch (FileNotFoundException e)
		{
			// TODO komunikat o braku pliku
			e.printStackTrace();
		}
	}

	/** loads contents of file into program structures
	 * @param fileName file to read
	 * @param filterWords should omit unmeaningful words
	 * @param filterSize should omit 1 letter words */
	public void loadFile(String fileName, boolean filterWords, boolean filterSize)
	{
		Scanner fileScanner;
		try
		{
			BufferedReader reader = new BufferedReader(new FileReader(fileName));
			String strLine;
			while ((strLine = reader.readLine()) != null)
			{
				fileScanner = IOManager.getStringScanner(strLine, IOManager.WORDS_NO_NUMBERS_DELIM_REGEX);
				DbSequence dbSequence = new DbSequence();
				while (fileScanner.hasNext())
				{
					String word = fileScanner.next().toLowerCase();
					if (filterSize && word.length() < 2)
					{
						continue;
					}
					if (filterWords && (stopwords != null && stopwords.contains(word)))
					{
						continue;
					}

					dbSequence.addElement(MyUtils.stem(word));
				}
				sequences.add(dbSequence);
			}
			reader.close();
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
			return;
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/** loads contents of file into single database sequence */
	public DbSequence loadSequenceFromFile(String fileName)
	{
		StringBuffer fileText = new StringBuffer();
		try
		{
			BufferedReader reader = new BufferedReader(new FileReader(fileName));
			String strLine;
			while ((strLine = reader.readLine()) != null)
			{
				fileText.append(strLine);
				fileText.append(" ");
			}
			reader.close();
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
			return null;
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return loadSequence(fileText.toString());
	}

	/** Loads contents from given web pages and saves them in files
	 * @param dirName name of directore to save contents of web pages
	 * @param urls set of web pages urls */
	public void loadUrls(Set<String> urls, String dirName)
	{
		int k = 0;
		for (String url : urls)
		{
			Document doc;
			try
			{
				doc = Jsoup.connect(url).get();
				sequences.add(loadSequence(doc.text()));
				k++;
				BufferedWriter writer = new BufferedWriter(new FileWriter(dirName + k + ".txt"));
				writer.write(doc.text());
				writer.close();
				logger.log(Level.INFO, "sequence from: " + url + " added.", "creating");
			}
			catch (IOException e)
			{
				logger.log(Level.INFO, "error loading : " + url, "creating");
				e.printStackTrace();
			}
		}

	}

	/** Loads database sequence from given text
	 * @param sequenceText text of document to be converted into sequence
	 * @return database sequence */
	public static DbSequence loadSequence(String sequenceText)
	{
		Scanner scanner = IOManager.getStringScanner(sequenceText, IOManager.WORDS_NO_NUMBERS_DELIM_REGEX);
		DbSequence dbSequence = new DbSequence();
		while (scanner.hasNext())
		{
			String word = scanner.next().toLowerCase();
			if ((stopwords != null && stopwords.contains(word)) || word.length() < 2)
			{
				continue;
			}
			dbSequence.addElement(word);
		}
		return dbSequence;
	}

	/** @return distinct words from all considered sequences */
	public Set<Sequence> get1Sequences()
	{
		Set<Sequence> resultSet = new HashSet<Sequence>();
		for (int i = 0; i < sequences.size(); i++)
		{
			if (excluded && i >= inactiveIndexBegin && i < inactiveIndexEnd)
			{
				continue;
			}
			resultSet.addAll(sequences.get(i).get1SequenceSet());
		}
		return resultSet;
	}

	public Set<Sequence> get2Sequences(int maxDist, Set<Sequence> seedSet)
	{
		Set<Sequence> resultSet = new HashSet<Sequence>();
		for (DbSequence dbSeq : sequences)
		{
			resultSet.addAll(dbSeq.get2SequenceSet(maxDist, seedSet));
		}
		return resultSet;
	}

	/** Prints average lengths and sizes of sequences */
	public void printStats()
	{
		int seqLen = 0, seqSize = 0;
		for (DbSequence seq : sequences)
		{
			seqLen += seq.getLength();
			seqSize += seq.getSize();
		}
		double avgLen = (double) seqLen / sequences.size();
		double avgSize = (double) seqSize / sequences.size();
		logger.log(Level.INFO, "Avg len:" + avgLen + ", avg size:" + avgSize, "statistics");
	}

	/** tells not to consider some sequences
	 * @param whichPart part of sequences to omit
	 * @param allParts number of parts that all sequences consist of */
	public void exclude(int whichPart, int allParts)
	{
		int partSize = sequences.size() / allParts;
		excluded = true;
		inactiveIndexBegin = (whichPart - 1) * partSize;
		inactiveIndexEnd = inactiveIndexBegin + partSize;
	}

	/** switches omitting sequences
	 * @param b */
	public void setExclude(boolean b)
	{
		this.excluded = b;
	}

	public void printSequences()
	{
		for (DbSequence seq : sequences)
		{
			System.out.println(seq);
		}
	}


	public List<DbSequence> getSequences()
	{
		if (excluded)
		{
			ArrayList<DbSequence> result = new ArrayList<DbSequence>(sequences.subList(0, inactiveIndexBegin));
			result.addAll(sequences.subList(inactiveIndexEnd, sequences.size()));
			return result;
		}
		return sequences;
	}

	/** @return sequences not considered in seqeuncing (GSP) algorithm */
	public List<DbSequence> getExcludedSequences()
	{
		if (excluded)
		{
			return sequences.subList(inactiveIndexBegin, inactiveIndexEnd);
		}
		return new ArrayList<DbSequence>();
	}

	public int getSize()
	{
		if (excluded)
		{
			return sequences.size() - (inactiveIndexEnd - inactiveIndexBegin);
		}
		return sequences.size();
	}


}
